/**
 * 
 */
package qy.jalgotrade.technical.ma;

import java.time.ZonedDateTime;

import org.apache.commons.math3.stat.StatUtils;

import it.unimi.dsi.fastutil.doubles.DoubleList;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.technical.EventBasedFilter;
import qy.jalgotrade.technical.EventWindow;

/**
 * <pre>
 * Simple Moving Average filter.
 * 
 * This is the formula I'm using to calculate the averages based on previous ones.
 * 1 2 3 4
 * x x x
 *   x x x
 * 
 * avg0 = (a + b + c) / 3
 * avg1 = (b + c + d) / 3
 * 
 * avg0 = avg1 + x
 * (a + b + c) / 3 = (b + c + d) / 3 + x
 * a/3 + b/3 + c/3 = b/3 + c/3 + d/3 + x
 * a/3 = d/3 + x
 * x = a/3 - d/3
 * 
 * avg1 = avg0 - x
 * avg1 = avg0 + d/3 - a/3
 * </pre>
 * 
 * @author c-geo
 *
 */
public class SMA extends EventBasedFilter<Double> {

	public static class SMAEventWindow extends EventWindow<Double> {

		private double __value;

		/**
		 * 
		 * @param period
		 */
		public SMAEventWindow(int period) {

			super(period, Double.class);
			assert period > 0;
			__value = Double.NaN;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * qy.jalgotrade.technical.EventBasedFilter.EventWindow#onNewValue(java.time.ZonedDateTime,
		 * java.lang.Object)
		 */
		@Override
		public void onNewValue(ZonedDateTime dateTime, Object value) {

			double firstValue = Double.NaN;
			if (((DoubleList) getValues()).size() > 0) {
				firstValue = ((DoubleList) getValues()).getDouble(0);
				assert !Double.isNaN(firstValue);
			}

			super.onNewValue(dateTime, value);

			if (value != null && windowFull()) {
				if (Double.isNaN(__value)) {

//					__value = StatUtils.mean(((DoubleList) getValues()).toDoubleArray());
					__value = StatUtils.mean((double[]) toPrimitiveArray());
				} else {
					__value = __value + (double) value / getWindowSize() - firstValue / getWindowSize();
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see qy.jalgotrade.technical.EventBasedFilter.EventWindow#getValue()
		 */
		@Override
		public Double getValue() {

			return __value;
		}
	}

	/**
	 * 
	 * @param dataSeries The DataSeries instance being filtered.
	 * @param period     The number of values to use to calculate the SMA.
	 * @throws Exception
	 */
	public SMA(SequenceDataSeries<Double> dataSeries, int period) throws Exception {

		this(dataSeries, period, 0);
	}

	/**
	 * 
	 * @param dataSeries The DataSeries instance being filtered.
	 * @param period     The number of values to use to calculate the SMA.
	 * @param maxLen     The maximum number of values to hold. Once a bounded length is full, when new
	 *                   items are added, a corresponding number of items are discarded from the
	 *                   opposite end. If None then dataseries.DEFAULT_MAX_LEN is used.
	 * @throws Exception
	 */
	public SMA(SequenceDataSeries<Double> dataSeries, int period, int maxLen) throws Exception {

		super(maxLen);
		SMAEventWindow win = new SMAEventWindow(period);
		_init(dataSeries, win);
	}
}
